﻿////////////////////////////////////////////////////////////////////////////////////////////
// File: Mouse.cs                                                                         //
// Author: Mike Binnix                                                                    //
// Desc: Class representing a computer mouse.  Wraps the XNA input methods for            //
//             mice into a single class to more easily check a users input.               //
////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace SlothEngine.Input
{
    public class InputManager
    {
        private List<IInputDevice> deviceList;

        public InputManager()
        {
            deviceList = new List<IInputDevice>();
        }

        #region General Methods
        /// <summary>
        /// Updates all devices.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            foreach (IInputDevice device in deviceList)
                device.Update(gameTime);
        }
        /// <summary>
        /// Removes all input devices from the manager.
        /// </summary>
        public void Reset()
        {
            deviceList.Clear();
        }
        /// <summary>
        /// Removes all input devices for a given player.
        /// </summary>
        /// <param name="player">Player to remove input devices from.</param>
        public void RemovePlayer(PlayerID player)
        {
            if (player == PlayerID.All)
                Reset();
            else
                foreach (IInputDevice device in deviceList)
                    if (device.Player == player)
                        deviceList.Remove(device);
        }
        /// <summary>
        /// Checks whether the input manager contains an input device for a certain player.
        /// </summary>
        /// <param name="player">PlayerID to find.</param>
        /// <returns>Returns true if inputer manager has a device for given player.  Otherwise returns false.</returns>
        public bool ContainsPlayer(PlayerID player)
        {
            foreach (IInputDevice device in deviceList)
                if (device.Player == player)
                    return true;

            return false;
        }
        #endregion

        #region Keyboard Methods
        /// <summary>
        /// Adds a new keyboard object.
        /// </summary>
        /// <param name="device">Object to be added.</param>
        public void AddKeyboard(XNAKeyboard device)
        {
            deviceList.Add(device);
        }
        /// <summary>
        /// Removes all keyboards from input manager.
        /// </summary>
        public void RemoveKeyboard()
        {
            foreach (IInputDevice device in deviceList)
                if (device is XNAKeyboard)
                    deviceList.Remove(device);
        }
        /// <summary>
        /// Removes the keyboard object for a given player.
        /// </summary>
        /// <param name="player">Player that contains the keyboard object.</param>
        public void RemoveKeyboard(PlayerID player)
        {
            if (player == PlayerID.All)
                RemoveKeyboard();
            else
                foreach (IInputDevice device in deviceList)
                    if (device is XNAKeyboard && device.Player == player)
                        deviceList.Remove(device);
        }
        /// <summary>
        /// Check if a key has been pressed for the given players input.  PlayerID.All checks against all keyboards.
        /// </summary>
        /// <param name="player">Players to check.</param>
        /// <param name="key">Key to check.</param>
        /// <returns>Returns true if key was pressed against any of the player keyboards.  Otherwise returns false.</returns>
        public bool KeyPressed(PlayerID player, Keys key)
        {
            List<XNAKeyboard> keyboardList = new List<XNAKeyboard>();

            // Add the keyboard objects to check against to a list
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAKeyboard)
                        keyboardList.Add((XNAKeyboard)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAKeyboard && device.Player == player)
                        keyboardList.Add((XNAKeyboard)device);
            }

            // Throw exception if there were not matching input devices found.
            if (keyboardList.Count() == 0)
                throw new Exception("Could not find any keyboard devices for PlayerID." + player.ToString());

            // Check if key has been pressed on any of the specified keyboards.
            foreach (XNAKeyboard keyboard in keyboardList)
                if (keyboard.KeyPressed(key))
                    return true;

            return false;
        }
        /// <summary>
        /// Check if a key has been released for the given players input.  PlayerID.All checks against all keyboards.
        /// </summary>
        /// <param name="player">Players input device to check.</param>
        /// <param name="key">Key to check.</param>
        /// <returns>Returns true if key was released against any of the player keyboards.  Otherwise returns false.</returns>
        public bool KeyReleased(PlayerID player, Keys key)
        {
            List<XNAKeyboard> keyboardList = new List<XNAKeyboard>();

            // Add the keyboard objects to check against to a list.
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAKeyboard)
                        keyboardList.Add((XNAKeyboard)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAKeyboard && device.Player == player)
                        keyboardList.Add((XNAKeyboard)device);
            }

            // Throw exception if there were not matching input devices found.
            if (keyboardList.Count() == 0)
                throw new Exception("Could not find any keyboard devices for PlayerID." + player.ToString());

            // Check if key has been released on any of the specified keyboard objects
            foreach (XNAKeyboard keyboard in keyboardList)
                if (keyboard.KeyReleased(key))
                    return true;

            return false;
        }
        /// <summary>
        /// Check if a key has been held for the given players input.  PlayerID.All checks against all keyboards.
        /// </summary>
        /// <param name="player">Players input device to check.</param>
        /// <param name="key">Key to check.</param>
        /// <returns>Returns true if key was held against any of the player keyboards.  Otherwise returns false.</returns>
        public bool KeyHeld(PlayerID player, Keys key)
        {
            List<XNAKeyboard> keyboardList = new List<XNAKeyboard>();

            // Add the keyboard objects to check against to a list.
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAKeyboard)
                        keyboardList.Add((XNAKeyboard)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAKeyboard && device.Player == player)
                        keyboardList.Add((XNAKeyboard)device);
            }

            // Throw exception if there were not matching input devices found.
            if (keyboardList.Count() == 0)
                throw new Exception("Could not find any keyboard devices for PlayerID." + player.ToString());

            // Check if button has been held on any of the specified keyboard objects
            foreach (XNAKeyboard keyboard in keyboardList)
                if (keyboard.KeyHeld(key))
                    return true;

            return false;
        }
        #endregion

        #region Mouse Methods
        /// <summary>
        /// Adds a new mouse object.
        /// </summary>
        /// <param name="device">object to be added.</param>
        public void AddMouse(XNAMouse device)
        {
            deviceList.Add(device);
        }
        /// <summary>
        /// Removes all mice.
        /// </summary>
        public void RemoveMouse()
        {
            foreach (IInputDevice device in deviceList)
                if (device is XNAMouse)
                    deviceList.Remove(device);
        }
        /// <summary>
        /// Removes the mouse object for a given player
        /// </summary>
        /// <param name="player">Player that contains the mouse object.</param>
        public void RemoveMouse(PlayerID player)
        {
            if (player == PlayerID.All)
                RemoveMouse();
            else
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse && device.Player == player)
                        deviceList.Remove(device);
        }
        /// <summary>
        /// Check if a button has been pressed for the given players input.  PlayerID.All checks against all mice.
        /// </summary>
        /// <param name="player">Player to check.</param>
        /// <param name="button">Button to check.</param>
        /// <returns>Returns true if button was pressed against any of the player mice.  Otherwise returns false.</returns>
        public bool ButtonPressed(PlayerID player, MouseButtons button)
        {
            List<XNAMouse> mouseList = new List<XNAMouse>();

            // Add the Mouse objects to check against to a list.
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse)
                        mouseList.Add((XNAMouse)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse && device.Player == player)
                        mouseList.Add((XNAMouse)device);
            }

            // Throw exception if there were not matching input devices found.
            if (mouseList.Count() == 0)
                throw new Exception("Could not find any mouse devices for PlayerID." + player.ToString());

            // Check if button has been pressed on any of the specified mouse objects
            foreach (XNAMouse mouse in mouseList)
                if (mouse.ButtonPressed(button))
                    return true;

            return false;
        }
        /// <summary>
        /// Check if a button has been released for the given players input.  PlayerID.All checks against all mice.
        /// </summary>
        /// <param name="player">Players input device to check.</param>
        /// <param name="button">Button to check.</param>
        /// <returns>Returns true if button was released against any of the player mice.  Otherwise returns false.</returns>
        public bool ButtonReleased(PlayerID player, MouseButtons button)
        {
            List<XNAMouse> mouseList = new List<XNAMouse>();

            // Add the mouse objects to check against to a list.
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse)
                        mouseList.Add((XNAMouse)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse && device.Player == player)
                        mouseList.Add((XNAMouse)device);
            }

            // Throw exception if there were not matching input devices found.
            if (mouseList.Count() == 0)
                throw new Exception("Could not find any mouse devices for PlayerID." + player.ToString());

            // Check if button has been released on any of the specified mouse objects
            foreach (XNAMouse mouse in mouseList)
                if (mouse.ButtonReleased(button))
                    return true;

            return false;
        }
        /// <summary>
        /// Check if a button has been held for the given players input.  PlayerID.All checks against all keyboards.
        /// </summary>
        /// <param name="player">Players input device to check.</param>
        /// <param name="button">Button to check.</param>
        /// <returns>Returns true if button was held against any of the player mice.  Otherwise returns false.</returns>
        public bool ButtonHeld(PlayerID player, MouseButtons button)
        {
            List<XNAMouse> mouseList = new List<XNAMouse>();

            // Add the mouse objects to check against to a list.
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse)
                        mouseList.Add((XNAMouse)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse && device.Player == player)
                        mouseList.Add((XNAMouse)device);
            }

            // Throw exception if there were not matching input devices found.
            if (mouseList.Count() == 0)
                throw new Exception("Could not find any mouse devices for PlayerID." + player.ToString());

            // Check if button has been held on any of the specified mouse objects
            foreach (XNAMouse mouse in mouseList)
                if (mouse.ButtonHeld(button))
                    return true;

            return false;
        }
        /// <summary>
        /// Checks the state of the scroll wheel fora  given players input.  PlayerID.All checks against all mice.
        /// </summary>
        /// <param name="player">Players input device to check.</param>
        /// <param name="state">State to check against.</param>
        /// <returns>Returns true if scroll wheel state matches.  Otherwise returns false.</returns>
        public bool ScrollWheelState(PlayerID player, MouseScrollState state)
        {
            List<XNAMouse> mouseList = new List<XNAMouse>();

            // Add the mouse objects to check against to a list
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse)
                        mouseList.Add((XNAMouse)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse && device.Player == player)
                        mouseList.Add((XNAMouse)device);
            }

            // Throw exception if there were not matching input devices found.
            if (mouseList.Count() == 0)
                throw new Exception("Could not find any mouse devices for PlayerID." + player.ToString());

            // Check if scroll state matches for any of the specified objects.
            foreach (XNAMouse mouse in mouseList)
                if (mouse.ScrollState(state))
                    return true;

            return false;
        }
        /// <summary>
        /// Returns the current position of the mouse.
        /// </summary>
        /// <param name="player">Player to check.</param>
        /// <returns>Position as a vector2.</returns>
        public Vector2 MousePosition(PlayerID player)
        {
            List<XNAMouse> mouseList = new List<XNAMouse>();
            Vector2 position = Vector2.Zero;

            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse)
                        mouseList.Add((XNAMouse)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAMouse && device.Player == player)
                        mouseList.Add((XNAMouse)device);
            }

            if (mouseList.Count() == 0)
                throw new Exception("Could not find any mouse devices for PlayerID." + player.ToString());

            foreach (XNAMouse mouse in mouseList)
                return position = mouse.Position;

            return position;
        }
        #endregion

        #region Gamepad Methods
        /// <summary>
        /// Adds a new gamepad object.
        /// </summary>
        /// <param name="device">Object to be added.</param>
        public void AddGamepad(XNAGamepad device)
        {
            deviceList.Add(device);
        }
        /// <summary>
        /// Removes all gamepads.
        /// </summary>
        public void RemoveGamepad()
        {
            foreach (IInputDevice device in deviceList)
                if (device is XNAGamepad)
                    deviceList.Remove(device);
        }
        /// <summary>
        /// Removes the gamepad object for the given player
        /// </summary>
        /// <param name="player">Player that contains the gamepad object.</param>
        public void RemoveGamepad(PlayerID player)
        {
            if (player == PlayerID.All)
                RemoveGamepad();
            else
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad && device.Player == player)
                        deviceList.Remove(device);
        }
        /// <summary>
        /// Checks if a button has been pressed for the given players input.  PlayerID.All checks against all gamepads.
        /// </summary>
        /// <param name="player">Players to check.</param>
        /// <param name="button">Button to check.</param>
        /// <returns>Returns true if button was pressed against any of the player gamepads.  Otherwise returns false.</returns>
        public bool ButtonPressed(PlayerID player, Buttons button)
        {
            List<XNAGamepad> gamepadList = new List<XNAGamepad>();

            // Add the gamepad objects to check against
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad)
                        gamepadList.Add((XNAGamepad)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad && device.Player == player)
                        gamepadList.Add((XNAGamepad)device);
            }

            // Throw exception if there were no matching input devices found.
            if (gamepadList.Count() == 0)
                throw new Exception("Could not find any gamepad devices for PlayerID." + player.ToString());

            // Check if button has been pressed on any of the specified gamepad objects
            foreach (XNAGamepad gamepad in gamepadList)
                if (gamepad.ButtonPressed(button))
                    return true;

            return false;
        }
        /// <summary>
        /// Checks if a button has been released for the given player.  PlayerID.All checks against all gamepads.
        /// </summary>
        /// <param name="player">Player to check.</param>
        /// <param name="button">Button to check.</param>
        /// <returns>Returns true if button was released against any of the player gamepads.  Otherwise returns false.</returns>
        public bool ButtonReleased(PlayerID player, Buttons button)
        {
            List<XNAGamepad> gamepadList = new List<XNAGamepad>();

            // Add gamepad objects to check against
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad)
                        gamepadList.Add((XNAGamepad)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad && device.Player == player)
                        gamepadList.Add((XNAGamepad)device);
            }

            // Throw exception if there were no matching input devices found.
            if (gamepadList.Count() == 0)
                throw new Exception("Could not find any gamepad devices for PlayerID." + player.ToString());

            // Check if button has been released on any of the specified gamepad objects
            foreach (XNAGamepad gamepad in gamepadList)
                if (gamepad.ButtonReleased(button))
                    return true;

            return false;
        }
        /// <summary>
        /// Checks if a button has been held for a given player.  PlayerID.All checks against all keyboards.
        /// </summary>
        /// <param name="player">Player to check.</param>
        /// <param name="button">Button to check.</param>
        /// <returns>Returns true if button was held against any of the player gamepads.  Otherwise returns false.</returns>
        public bool ButtonHeld(PlayerID player, Buttons button)
        {
            List<XNAGamepad> gamepadList = new List<XNAGamepad>();

            // Add gamepad objects to check against
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad)
                        gamepadList.Add((XNAGamepad)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad && device.Player == player)
                        gamepadList.Add((XNAGamepad)device);
            }

            // Throw exception if there were no matching input devices found
            if (gamepadList.Count() == 0)
                throw new Exception("Could not find any gamepad devices for PlayerID." + player.ToString());

            // Check if button has been held on any of the specified gamepad objects
            foreach (XNAGamepad gamepad in gamepadList)
                if (gamepad.ButtonHeld(button))
                    return true;

            return false;
        }
        /// <summary>
        /// Vibates the given players gamepads by the specified amounts.  PlayerID.All vibrates all gamepads.
        /// </summary>
        /// <param name="player">Player to vibrate.</param>
        /// <param name="lowFrequency">Frequency that the left motor should vibrate at.</param>
        /// <param name="highFrequency">Frequency that the right motor should vibrate at.</param>
        public void VibrateGamepad(PlayerID player, float lowFrequency, float highFrequency)
        {
            List<XNAGamepad> gamepadList = new List<XNAGamepad>();

            // Add gamepad objects to check against
            if (player == PlayerID.All)
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad)
                        gamepadList.Add((XNAGamepad)device);
            }
            else
            {
                foreach (IInputDevice device in deviceList)
                    if (device is XNAGamepad && device.Player == player)
                        gamepadList.Add((XNAGamepad)device);
            }

            // Throw exception if there were no matching input devices
            if (gamepadList.Count() == 0)
                throw new Exception("Could not find any gamepad devices for PlayerID." + player.ToString());

            // Apply vibration to all of the specified gamepad objects
            foreach (XNAGamepad gamepad in gamepadList)
                gamepad.Vibrate(lowFrequency, highFrequency);
        }
        /// <summary>
        /// Gets the amount that a specified trigger is pushed in for a given player.  Can not be used to with PlayerID.All.
        /// </summary>
        /// <param name="player">Player to check.</param>
        /// <param name="trigger">Trigger to check.</param>
        /// <returns>Returns the amount that the trigger is pressed in.  0.0f is not pressed at all, 1.0f is fully pressed.</returns>
        public float TriggerIntensity(PlayerID player, Trigger trigger)
        {
            XNAGamepad gamepad = null;

            // Throw exception if user tries to get the trigger intensity for all gamepads.
            if (player == PlayerID.All)
                throw new Exception("Can not use PlayerID." + player.ToString() + " when trying to find trigger intensity.");

            // Find gamepad for given player.  Throw exception if there are multiple gamespads with the same playerID
            foreach (IInputDevice device in deviceList)
                if (device is XNAGamepad && device.Player == player)
                    if (gamepad == null)
                        gamepad = (XNAGamepad)device;
                    else
                        throw new Exception("Multiple gamepads found with PlayerID." + player.ToString());

            return gamepad.TriggerValue(trigger);
        }
        /// <summary>
        /// Finds the position of a thumbstick for a given player.  Does not work with PlayerID.All
        /// </summary>
        /// <param name="player">Player to check.</param>
        /// <param name="stick">Thumbstick to check.</param>
        /// <returns>Returns the x, y position of the thumbstick.  x and y range is -1.0f - 1.0f.</returns>
        public Vector2 StickPosition(PlayerID player, ThumbStick stick)
        {
            XNAGamepad gamepad = null;

            // Throw exception if user tries to get the trigger intensity for all gamepads.
            if (player == PlayerID.All)
                throw new Exception("Can not use PlayerID." + player.ToString() + " when trying to find thumbstick position.");

            // Find gamepad for given player.  Throw exception if there are multiple gamespads with the same playerID
            foreach (IInputDevice device in deviceList)
                if (device is XNAGamepad && device.Player == player)
                    if (gamepad == null)
                        gamepad = (XNAGamepad)device;
                    else
                        throw new Exception("Multiple gamepads found with PlayerID." + player.ToString());

            return gamepad.ThumbstickPosition(stick);
        }
        /// <summary>
        /// Finds the angle that a thumbstick is pressed in for a given player.  Does not work with PlayerID.All
        /// </summary>
        /// <param name="player">Player to check.</param>
        /// <param name="stick">Thumbstick to check.</param>
        /// <returns>Returns the angle in radians of the thumbstick.  If thumbstick not pressed in a direction returns NaN.</returns>
        public float StickAngle(PlayerID player, ThumbStick stick)
        {
            XNAGamepad gamepad = null;

            // Throw exception if user tries to get the angel for all gamepads.
            if (player == PlayerID.All)
                throw new Exception("Can not use PlayerID." + player.ToString() + " when trying to find thumbstick angle.");

            // Find gamepad for a given player.  Throw exception if there are multiple gamepads with the same playerID
            foreach (IInputDevice device in deviceList)
                if (device is XNAGamepad && device.Player == player)
                    if (gamepad == null)
                        gamepad = (XNAGamepad)device;
                    else
                        throw new Exception("Multiple gamepads found with PlayerID." + player.ToString());

            return gamepad.ThumbstickAngle(stick);
        }
        /// <summary>
        /// Checks if any gamepad devices are disconnected.
        /// </summary>
        /// <returns>Returns the PlayerID of the disconnected gamepad.  If no gamepads are disconnected returns PlayerID.All</returns>
        public PlayerID Disconnected()
        {
            XNAGamepad tempPad = null;

            foreach(IInputDevice device in deviceList)
                if (device is XNAGamepad)
                {
                    tempPad = (XNAGamepad)device;

                    if (!tempPad.Connected)
                        return tempPad.Player;
                }

            return PlayerID.All;
        }
        #endregion
    }
}
